// Copyright (c) 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "media/capture/video/video_capture_buffer_pool_impl.h"

#include <memory>

#include "base/logging.h"
#include "base/memory/ptr_util.h"
#include "build/build_config.h"
#include "media/capture/video/video_capture_buffer_handle.h"
#include "media/capture/video/video_capture_buffer_tracker.h"
#include "ui/gfx/buffer_format_util.h"

namespace media {

VideoCaptureBufferPoolImpl::VideoCaptureBufferPoolImpl(
    std::unique_ptr<VideoCaptureBufferTrackerFactory> buffer_tracker_factory,
    int count)
    : count_(count)
    , next_buffer_id_(0)
    , last_relinquished_buffer_id_(kInvalidId)
    , buffer_tracker_factory_(std::move(buffer_tracker_factory))
{
    DCHECK_GT(count, 0);
}

VideoCaptureBufferPoolImpl::~VideoCaptureBufferPoolImpl() { }

mojo::ScopedSharedBufferHandle
VideoCaptureBufferPoolImpl::GetHandleForInterProcessTransit(int buffer_id)
{
    base::AutoLock lock(lock_);

    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return mojo::ScopedSharedBufferHandle();
    }
    return tracker->GetHandleForTransit();
}

base::SharedMemoryHandle
VideoCaptureBufferPoolImpl::GetNonOwnedSharedMemoryHandleForLegacyIPC(
    int buffer_id)
{
    base::AutoLock lock(lock_);

    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return base::SharedMemoryHandle();
    }
    return tracker->GetNonOwnedSharedMemoryHandleForLegacyIPC();
}

std::unique_ptr<VideoCaptureBufferHandle>
VideoCaptureBufferPoolImpl::GetHandleForInProcessAccess(int buffer_id)
{
    base::AutoLock lock(lock_);

    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return nullptr;
    }

    return tracker->GetMemoryMappedAccess();
}

int VideoCaptureBufferPoolImpl::ReserveForProducer(
    const gfx::Size& dimensions,
    media::VideoPixelFormat format,
    media::VideoPixelStorage storage,
    int frame_feedback_id,
    int* buffer_id_to_drop)
{
    base::AutoLock lock(lock_);
    return ReserveForProducerInternal(dimensions, format, storage,
        frame_feedback_id, buffer_id_to_drop);
}

void VideoCaptureBufferPoolImpl::RelinquishProducerReservation(int buffer_id)
{
    base::AutoLock lock(lock_);
    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return;
    }
    DCHECK(tracker->held_by_producer());
    tracker->set_held_by_producer(false);
    last_relinquished_buffer_id_ = buffer_id;
}

void VideoCaptureBufferPoolImpl::HoldForConsumers(int buffer_id,
    int num_clients)
{
    base::AutoLock lock(lock_);
    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return;
    }
    DCHECK(tracker->held_by_producer());
    DCHECK(!tracker->consumer_hold_count());

    tracker->set_consumer_hold_count(num_clients);
    // Note: |held_by_producer()| will stay true until
    // RelinquishProducerReservation() (usually called by destructor of the object
    // wrapping this tracker, e.g. a media::VideoFrame).
}

void VideoCaptureBufferPoolImpl::RelinquishConsumerHold(int buffer_id,
    int num_clients)
{
    base::AutoLock lock(lock_);
    VideoCaptureBufferTracker* tracker = GetTracker(buffer_id);
    if (!tracker) {
        NOTREACHED() << "Invalid buffer_id.";
        return;
    }
    DCHECK_GE(tracker->consumer_hold_count(), num_clients);

    tracker->set_consumer_hold_count(tracker->consumer_hold_count() - num_clients);
}

int VideoCaptureBufferPoolImpl::ResurrectLastForProducer(
    const gfx::Size& dimensions,
    media::VideoPixelFormat format,
    media::VideoPixelStorage storage)
{
    base::AutoLock lock(lock_);

    // Return early if the last relinquished buffer has been re-used already.
    if (last_relinquished_buffer_id_ == kInvalidId)
        return kInvalidId;

    // If there are no consumers reading from this buffer, then it's safe to
    // provide this buffer back to the producer (because the producer may
    // potentially modify the content). Check that the expected dimensions,
    // format, and storage match.
    auto it = trackers_.find(last_relinquished_buffer_id_);
    DCHECK(it != trackers_.end());
    DCHECK(!it->second->held_by_producer());
    if (it->second->consumer_hold_count() == 0 && it->second->dimensions() == dimensions && it->second->pixel_format() == format && it->second->storage_type() == storage) {
        it->second->set_held_by_producer(true);
        const int resurrected_buffer_id = last_relinquished_buffer_id_;
        last_relinquished_buffer_id_ = kInvalidId;
        return resurrected_buffer_id;
    }

    return kInvalidId;
}

double VideoCaptureBufferPoolImpl::GetBufferPoolUtilization() const
{
    base::AutoLock lock(lock_);
    int num_buffers_held = 0;
    for (const auto& entry : trackers_) {
        VideoCaptureBufferTracker* const tracker = entry.second.get();
        if (tracker->held_by_producer() || tracker->consumer_hold_count() > 0)
            ++num_buffers_held;
    }
    return static_cast<double>(num_buffers_held) / count_;
}

int VideoCaptureBufferPoolImpl::ReserveForProducerInternal(
    const gfx::Size& dimensions,
    media::VideoPixelFormat pixel_format,
    media::VideoPixelStorage storage_type,
    int frame_feedback_id,
    int* buffer_id_to_drop)
{
    lock_.AssertAcquired();

    const size_t size_in_pixels = dimensions.GetArea();
    // Look for a tracker that's allocated, big enough, and not in use. Track the
    // largest one that's not big enough, in case we have to reallocate a tracker.
    *buffer_id_to_drop = kInvalidId;
    size_t largest_size_in_pixels = 0;
    auto tracker_of_last_resort = trackers_.end();
    auto tracker_to_drop = trackers_.end();
    for (auto it = trackers_.begin(); it != trackers_.end(); ++it) {
        VideoCaptureBufferTracker* const tracker = it->second.get();
        if (!tracker->consumer_hold_count() && !tracker->held_by_producer()) {
            if (tracker->max_pixel_count() >= size_in_pixels && (tracker->pixel_format() == pixel_format) && (tracker->storage_type() == storage_type)) {
                if (it->first == last_relinquished_buffer_id_) {
                    // This buffer would do just fine, but avoid returning it because the
                    // client may want to resurrect it. It will be returned perforce if
                    // the pool has reached it's maximum limit (see code below).
                    tracker_of_last_resort = it;
                    continue;
                }
                // Existing tracker is big enough and has correct format. Reuse it.
                tracker->set_dimensions(dimensions);
                tracker->set_held_by_producer(true);
                tracker->set_frame_feedback_id(frame_feedback_id);
                return it->first;
            }
            if (tracker->max_pixel_count() > largest_size_in_pixels) {
                largest_size_in_pixels = tracker->max_pixel_count();
                tracker_to_drop = it;
            }
        }
    }

    // Preferably grow the pool by creating a new tracker. If we're at maximum
    // size, then try using |tracker_of_last_resort| or reallocate by deleting an
    // existing one instead.
    if (trackers_.size() == static_cast<size_t>(count_)) {
        if (tracker_of_last_resort != trackers_.end()) {
            last_relinquished_buffer_id_ = kInvalidId;
            tracker_of_last_resort->second->set_dimensions(dimensions);
            tracker_of_last_resort->second->set_held_by_producer(true);
            tracker_of_last_resort->second->set_frame_feedback_id(frame_feedback_id);
            return tracker_of_last_resort->first;
        }
        if (tracker_to_drop == trackers_.end()) {
            // We're out of space, and can't find an unused tracker to reallocate.
            return kInvalidId;
        }
        if (tracker_to_drop->first == last_relinquished_buffer_id_)
            last_relinquished_buffer_id_ = kInvalidId;
        *buffer_id_to_drop = tracker_to_drop->first;
        trackers_.erase(tracker_to_drop);
    }

    // Create the new tracker.
    const int buffer_id = next_buffer_id_++;

    std::unique_ptr<VideoCaptureBufferTracker> tracker = buffer_tracker_factory_->CreateTracker(storage_type);
    // TODO(emircan): We pass the lock here to solve GMB allocation issue, see
    // crbug.com/545238.
    if (!tracker->Init(dimensions, pixel_format, storage_type, &lock_)) {
        DLOG(ERROR) << "Error initializing VideoCaptureBufferTracker";
        return kInvalidId;
    }

    tracker->set_held_by_producer(true);
    tracker->set_frame_feedback_id(frame_feedback_id);
    trackers_[buffer_id] = std::move(tracker);

    return buffer_id;
}

VideoCaptureBufferTracker* VideoCaptureBufferPoolImpl::GetTracker(
    int buffer_id)
{
    auto it = trackers_.find(buffer_id);
    return (it == trackers_.end()) ? nullptr : it->second.get();
}

} // namespace media
